﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SQLToolbox.Types;
using SQLToolbox.View.Types;
using SQLToolbox.Controller.Tools;
using SQLToolbox.View;
using System.Windows;
using SQLToolbox.View.UserControls;
using System.Windows.Threading;
using System.Threading;
using SQLToolbox.Lib;

namespace SQLToolbox.Controller
{
    public class SchemaComparatorController : ISchemaComparatorController
    {
        ISQLToolboxMainWindow main;
        IProgressTab tab;
        SchemaComparisonResultsViewer uc;
        public void Start(ISQLToolboxMainWindow main)
        {
            this.main = main;
            var connector = new DBConnector();


            var cfc = new DialogWindow(new ucConnectTwoDBs(new DBConnector()));
            //ConnectForComparison cfc = new ConnectForComparison(connector);
            if (main is Window)
                cfc.Owner = (Window)main;
            cfc.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            bool? result = cfc.ShowDialog();

            if (result == true)
            {
                var value = (Tuple<DBConnectionProperties, DBConnectionProperties>)cfc.Value;
                DBConnectionProperties left = value.a;//cfc.LeftValue;
                DBConnectionProperties right = value.b; //cfc.RightValue;
                tab = main.AddTab("Comparison", string.Format("{0}.{1} -> {2}.{3}",
                        left.ServerName, left.DatabaseName, right.ServerName, right.DatabaseName));
                uc = new SchemaComparisonResultsViewer(this);
                DoLongAction(() =>
                {
                    SchemaComaprison sc = CompareDatabases(left, right);
                    uc.Dispatcher.Invoke(DispatcherPriority.Normal,
                                      new Action(() =>
                                      {
                                          uc.ClearStatus();
                                          uc.SetSource(sc);
                                      }));
                });
                tab.Content = uc;
            }
        }



        //public void Start(ISQLToolboxMainWindow main)
        //{
        //    this.main = main;
        //    var connector = new DBConnector();

        //    DBConnectionProperties left = new DBConnectionProperties { DataSource = "MS SQL 2005", ServerName = @".\sql2005", Credentials = new ConnectionCredentials { IntegratedSecurity = true }, DatabaseName = "test" };
        //    DBConnectionProperties right = new DBConnectionProperties { DataSource = "MS SQL 2005", ServerName = @".\sql2005", Credentials = new ConnectionCredentials { IntegratedSecurity = true }, DatabaseName = "test1" };
        //    tab = main.AddTab("Comparison", string.Format("{0}.{1} -> {2}.{3}",
        //                left.ServerName, left.DatabaseName, right.ServerName, right.DatabaseName));
        //    SchemaComparisonResultsViewer uc = new SchemaComparisonResultsViewer(this);
        //    DoLongAction(() =>
        //               {
        //                   SchemaComaprison sc = CompareDatabases(left, right);
        //                   uc.Dispatcher.Invoke(DispatcherPriority.Normal,
        //                                     new Action(() => { uc.SetSource(sc); }));
        //               });
        //    tab.Content = uc;

        //}


        public void RedoComparison(DBConnectionProperties left, DBConnectionProperties right, Action<SchemaComaprison> callback)
        {
            DoLongAction(() =>
            {
                callback.Invoke(CompareDatabases(left, right));
            });
        }

        private SchemaComaprison CompareDatabases(DBConnectionProperties left, DBConnectionProperties right)
        {
            var connector = new DBConnector();

            SchemaComaprison cr = new SchemaComaprison { Left = left, Right = right };

            connector.DatabaseEventOccured += UpdateStatusInView;
            var ldb = connector.GetSchema(left);
            var rdb = connector.GetSchema(right);
            connector.DatabaseEventOccured -= UpdateStatusInView;

            uc.Dispatcher.Invoke(DispatcherPriority.Normal,
                                      new Action(() =>
                                      {
                                          uc.SetStatus("Comparing databases");
                                      }));
            return SchemaComparer.Compare(ldb, rdb, cr);
        }

        public void RedoObjectComparison(DBConnectionProperties left, DBConnectionProperties right, string name, SchemaObjectType type, Action<SchemaObjectComparison> callback)
        {
            DoLongAction(() =>
            {
                RedoObjectComparisonInLongAction(left, right, name, type, callback);
            });

        }



        private void RedoObjectComparisonInLongAction(DBConnectionProperties left, DBConnectionProperties right, string name, SchemaObjectType type, Action<SchemaObjectComparison> callback)
        {
            var connector = new DBConnector();
            SchemaObjectComparison crc = SchemaComparer.CompareObjects(connector.GetSchemaObject(left, type, name), connector.GetSchemaObject(right, type, name));
            callback.Invoke(crc);
        }

        public void MatchObjects(SchemaComaprison collection, SchemaObjectComparison cr, UpdateDirection direction, Action<SchemaObjectComparison> callback)
        {
            Script script;
            DoLongAction(() =>
            {
                var connector = new DBConnector();
                DBConnectionProperties sourceProperties, targetProperties;
                SortOutSourceAndTargetProperties(collection, direction, out sourceProperties, out targetProperties);

                SchemaObject source = null, target = null;
                SortOutSourceAndTargetSchemaObject(cr, direction, out source, out target);


                if (ObjectsChanged(connector, sourceProperties, targetProperties, source, target))
                    return;

                if (target == null)
                    script = source.Script;
                else if (source == null)
                    script = connector.CreateDropScript(targetProperties, target);
                else
                    script = connector.CreateAlterScript(sourceProperties, targetProperties, target);

                ShowExecuteScript(connector, targetProperties, script);

                RedoObjectComparisonInLongAction(collection.Left, collection.Right, cr.Name, cr.Type, callback);
            });
        }

        public void MatchSchemas(SchemaComaprison collection, UpdateDirection direction, Action<SchemaComaprison> callback)
        {
            DoLongAction(() =>
                {

                    uc.Dispatcher.Invoke(DispatcherPriority.Normal,
                                              new Action(() =>
                                              {
                                                  uc.SetStatus("Creating match script");
                                              }));


                    DBConnectionProperties sourceProperties, targetProperties;

                    SortOutSourceAndTargetProperties(collection, direction, out sourceProperties, out targetProperties);

                    var connector = new DBConnector();

                    connector.DatabaseEventOccured += UpdateStatusInView;
                    //Create a list of object to match

                    List<SQLToolbox.Lib.Tuple<SQLToolbox.Types.SchemaObjectBase, bool>> objsAction = new List<Tuple<SchemaObjectBase, bool>>();
                    Script script = new Script();
                    //foreach (var cr in collection)
                    //{
                    //    if (cr.Status != ComparisonStatus.Equal)
                    //    {
                    //        SchemaObject source = null, target = null;
                    //        SortOutSourceAndTargetSchemaObject(cr, direction, out source, out target);

                    //        if (ObjectsChanged(connector, sourceProperties, targetProperties, source, target))
                    //            return;

                    //        if (target == null)
                    //            //Create scripts  - add as is
                    //            script.AddRange(source.Script);
                    //        else if (source == null)
                    //            //Create drop script
                    //            objsAction.Add(Tuple.Create((SchemaObjectBase)target, false));
                    //        else
                    //            //Create alter script
                    //            objsAction.Add(Tuple.Create((SchemaObjectBase)target, true));
                    //    }


                    //}
                    #region Should be done with dependancy walker ?
                    Script lateCreates = new Script(); // I'm using it to delay the SP and etc creates after the changes in tables;
                    CreateScriptForCollection(connector,
                        collection.Where((s) => s.Type == SchemaObjectType.Table)
                        , direction, sourceProperties, targetProperties, script, objsAction);

                    CreateScriptForCollection(connector,
                        collection.Where((s) => s.Type == SchemaObjectType.View)
                        , direction, sourceProperties, targetProperties, script, objsAction);
                    CreateScriptForCollection(connector,
                        collection.Where((s) => s.Type != SchemaObjectType.Table && s.Type != SchemaObjectType.View)
                        , direction, sourceProperties, targetProperties, lateCreates, objsAction);

                    script.AddRange(connector.CreateMatchScript(sourceProperties, targetProperties, objsAction));//Create alter and drop scripts
                    connector.DatabaseEventOccured -= UpdateStatusInView;
                    script.AddRange(lateCreates); 
                    #endregion
                    if (ShowExecuteScript(connector, targetProperties, script))
                        RedoComparison(collection.Left, collection.Right, callback);
                });
        }

        private void CreateScriptForCollection(DBConnector connector, IEnumerable<SchemaObjectComparison> collection,
            UpdateDirection direction, DBConnectionProperties sourceProperties, DBConnectionProperties targetProperties,
            Script script, List<SQLToolbox.Lib.Tuple<SQLToolbox.Types.SchemaObjectBase, bool>> objsAction)
        {
            foreach (var cr in collection)
            {
                if (cr.Status != ComparisonStatus.Equal)
                {
                    SchemaObject source = null, target = null;
                    SortOutSourceAndTargetSchemaObject(cr, direction, out source, out target);

                    if (ObjectsChanged(connector, sourceProperties, targetProperties, source, target))
                        return;

                    if (target == null)
                        //Create scripts  - add as is
                        script.AddRange(source.Script);
                    else if (source == null)
                        //Create drop script
                        objsAction.Add(Tuple.Create((SchemaObjectBase)target, false));
                    else
                        //Create alter script
                        objsAction.Add(Tuple.Create((SchemaObjectBase)target, true));
                }


            }
        }
        private void SortOutSourceAndTargetSchemaObject(SchemaObjectComparison cr, UpdateDirection direction, out SchemaObject source, out SchemaObject target)
        {
            if (direction == UpdateDirection.LeftToRight)
            {
                source = cr.LeftObject;
                target = cr.RightObject;
            }
            else
            {
                source = cr.RightObject;
                target = cr.LeftObject;
            }
        }

        private void SortOutSourceAndTargetProperties(SchemaComaprison collection, UpdateDirection direction, out DBConnectionProperties source, out DBConnectionProperties target)
        {
            if (direction == UpdateDirection.LeftToRight)
            {
                source = collection.Left;
                target = collection.Right;
            }
            else
            {
                source = collection.Right;
                target = collection.Left;
            }
        }

        private void UpdateStatusInView(object sender, DBConnectorEventArgs e)
        {
            uc.Dispatcher.Invoke(DispatcherPriority.Normal,
                                                 new Action(() =>
                                                 {
                                                     uc.SetStatus(e.Message);
                                                 }));
        }

        private bool ShowExecuteScript(DBConnector connector, DBConnectionProperties targetProperties, Script script)
        {
            bool? result = (bool?)((Window)main).Dispatcher.Invoke(DispatcherPriority.Normal, new Func<bool?>(() =>
            {
                var d = new DialogWindow(new ScriptViewer(script.MergeForView()));
                d.Owner = (Window)main;
                return d.ShowDialog();
            }));

            if (result == true)
            {
                try
                {
                    ScriptExecuter.Execute(connector, targetProperties, script);
                    return true;
                }
                catch (SQLToolboxControllerException e)
                {
                    // TODO - nicer UI here
                    MessageBox.Show(e.Message, "Script failed during execution", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            return false;

        }

        private static bool ObjectsChanged(DBConnector connector, DBConnectionProperties sourceProperties, DBConnectionProperties targetProperties, SchemaObject source, SchemaObject target)
        {
            if (target != null)
            {
                SchemaObjectComparison crc = SchemaComparer.CompareObjects(connector.GetSchemaObject(targetProperties, target.Type, target.Name), target);
                if (crc.Status != ComparisonStatus.Equal)
                {
                    MessageBox.Show("The target object have changed", "Script generation error");
                    return true;
                }
            }
            if (source != null)
            {
                SchemaObjectComparison crc = SchemaComparer.CompareObjects(connector.GetSchemaObject(sourceProperties, source.Type, source.Name), source);
                if (crc.Status != ComparisonStatus.Equal)
                {
                    MessageBox.Show("The source object have changed", "Script generation error");
                    return true;
                }
            }
            return false;
        }


        private void DoLongAction(Action action)
        {
            tab.Dispatcher.Invoke(new Action(()=>tab.StartProgress()));
            ThreadStart start = delegate()
            {
                action.Invoke();
                tab.Dispatcher.Invoke(DispatcherPriority.Normal,
                                        new Action(() => tab.StopProgress()));
            };
            new Thread(start).Start();
        }





    }
}
